package ru.spb.osll.myfit.server.execute;

import com.thoughtworks.xstream.XStream;
import org.apache.log4j.Logger;
import ru.spb.osll.myfit.common.*;
import ru.spb.osll.myfit.server.Configurator;
import ru.spb.osll.myfit.server.SuiteUtils;
import ru.spb.osll.myfit.server.parser.SuiteHandler;
import ru.spb.osll.myfit.util.FileHelper;
import ru.spb.osll.myfit.util.StringUtils;

import javax.xml.parsers.ParserConfigurationException;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import static ru.spb.osll.myfit.server.SuiteUtils.getSuitePathFromId;

public class SuiteExecutor extends Thread {
  private final SuiteExecutingResult m_result = new SuiteExecutingResult();
  private PageExecutingServiceImpl m_pageExecutingService;
  private Logger m_logger = Logger.getLogger(getClass());
  private Configurator m_configurator = Configurator.getInstance();
  private Date m_time;
  private Suite m_suite = null;
  private SuiteHandler m_suiteHandler;

  private Map<String, String> m_metadata = new TreeMap<String, String>();
  private FileHelper m_helper;
  private Set<PageExecutor> m_pageExecutors;

  public SuiteExecutor(String id, SuiteHandler suiteHandler, Date time, PageExecutingServiceImpl pageExecutingService
          , FileHelper helper) {
    m_result.setId(id);
    m_suiteHandler = suiteHandler;
    m_pageExecutingService = pageExecutingService;
    m_time = time;
    m_helper = helper;

    loadSuite();
  }

  private void loadSuite() {
    String id = StringUtils.substringFromPattern(FileHelper.FORMAT_PATTERN, m_result.getId(), 1);

    m_logger.info("Executing suite " + id);

    String suiteFilePath = getSuitePathFromId(id);

    try {
      m_suite = m_suiteHandler.getSuite(suiteFilePath);
    } catch (FileNotFoundException e) {
      m_logger.error(e.getMessage() + "\n" + e.getStackTrace());
      return;
    } catch (ParserConfigurationException e) {
      m_logger.error(e.getMessage() + "\n" + e.getStackTrace());
      return;
    }

    m_suite.setId(id);

    List<String> pagesPaths = getPagesPaths();

    createExecutors(pagesPaths);

    fillPageExecutingResults();
  }

  private List<String> getPagesPaths() {
    List<String> pagesPaths = SuiteUtils.getSuitePagesFilteredByTags(m_suite);

    m_result.getCounts().setTotal(pagesPaths.size());
    return pagesPaths;
  }

  private void fillPageExecutingResults() {
    for (PageExecutor pageExecutor : m_pageExecutors) {
      m_result.addPageExecutingResult(pageExecutor.getPageExecutingResult());
    }
  }

  public String getExecutorId() {
    return m_result.getId();
  }

  private void executePages() {
    for (PageExecutor pageExecutor : m_pageExecutors) {
      pageExecutor.start();
      try {
        pageExecutor.join();
      } catch (InterruptedException e) {
        m_logger.info("Test " + pageExecutor.getTestId() + " execution interrupted");
      }
      PageProgressCounts counts = pageExecutor.getPageExecutingResult().getCounts();
      if (counts.getWrong() == 0 && counts.getExceptions() == 0) {
        m_result.getCounts().incrementPassed();
      } else {
        m_result.getCounts().incrementFailed();
      }
    }
  }

  private void createExecutors(List<String> pagesPaths) {
    m_pageExecutors = new LinkedHashSet<PageExecutor>();

    TreeMap<String, String> metadata = new TreeMap<String, String>();
    metadata.put(TestResultMetadataConstants.SUITE_RESULT, m_result.getId());

    m_pageExecutors.addAll(m_pageExecutingService.createPageExecutorsForSuite(pagesPaths, metadata, m_result.getId()));
  }

  private void saveResultToDisk() {
    try {
      String suiteResultFilePath = m_configurator.getSuiteResultsDirectory() + File.separator + m_result.getId() + File.separator + m_suite.getId() + ".xml";

      FileWriter fileWriter = new FileWriter(suiteResultFilePath);
      BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);

      XStream xStream = new XStream();
      xStream.processAnnotations(SuiteExecutingResult.class);

      bufferedWriter.write(xStream.toXML(m_result));

      bufferedWriter.close();
    } catch (IOException e) {
      m_logger.error("Could not write suite file with id " + m_suite.getId());
    }
  }

  public SuiteExecutingResult getSuiteExecutingResult() {
    SuiteExecutingResult result;
    synchronized (m_result) {
      result = new SuiteExecutingResult(m_result);
      result.setName(m_suite.getId());
      result.setStartTime(m_time);
      result.setMetadata(m_metadata);
    }
    return result;
  }

  @Override
  public void run() {
    synchronized (m_result) {
      m_result.setState(ExecutingResult.ExecutingState.PROGRESS);
    }

    executePages();

    synchronized (m_result) {
      m_result.setState(ExecutingResult.ExecutingState.FINISH);
    }

    saveMetadata();

    saveResultToDisk();
  }

  private void saveMetadata() {
    String metadataFilePath = m_configurator.getSuiteResultsDirectory() + File.separator + m_result.getId() + File.separator + m_suite.getId() + ".metadata";
    Date endTime = new Date();
    m_metadata.put(TestResultMetadataConstants.END_TIME, new SimpleDateFormat(TestResultMetadataConstants.METADATA_DATE_FORMAT, Locale.ENGLISH).format(endTime));
    long duration = endTime.getTime() - m_time.getTime();
    m_metadata.put(TestResultMetadataConstants.DURATION, StringUtils.dateDiffToString(duration));

    TestResultMetadataDao dao = new TestResultMetadataDao();
    OutputStream stream = null;
    try {
      stream = m_helper.openFile(metadataFilePath);
      dao.save(m_metadata, stream);
    } catch (IOException e) {
      m_logger.error("Could not save metadata for " + m_result.getId());
    } finally {
      m_helper.close(stream);
    }
  }

  public Map<String, String> getMetadata() {
    return m_metadata;
  }

  public void setMetadata(Map<String, String> m_metadata) {
    this.m_metadata = m_metadata;
  }
}
